#ifndef _ZASCIIZ_H_
#define _ZASCIIZ_H_

#include <cstring>
#include <cmath>
#include <cfloat>

/*!
 * Удобный и быстрый класс для работы с ASCIIZ-строкой
 */
class AsciiZ
{
public:
    //==============================================
    //  КОНСТРУКТОРЫ
    //==============================================
        //! \brief Заменим конструктор по умолчанию
        AsciiZ() {
            init();
        }
        //! \brief Создаем объект из части const char *
        AsciiZ(const char *src, int start=0, int count=0) {
            init();
            append(start, count, src);
        }

        //! \brief Создаем объект из части другого объекта
        AsciiZ(const AsciiZ &src, int start, int count) {
            init();
            int src_len = src.length();
            const char *src_data = src.data();
            int i=start;
            while (i<src_len) {
                if (--count < 0) break;
                append(*(src_data + i++));
            }
        }

        //! \brief Заменим стандартный конструктор копирования
        AsciiZ(const AsciiZ &other) {
            init();
            append(other);
        }

        // формирование строки минимум в digits разрядов из числа num в представлении radix
        AsciiZ(unsigned long num, int radix=10, int digits=0)
        {
            init();

            if (radix<2) radix = 2;

            if (num || digits>0) {
                while (num || digits>0)
                {
                    char ch = num%radix;
                    num = num/radix;
                    if (ch<10) append(ch+0x30);
                    else append(ch+0x41);
                    if (digits>0) --digits;
                }
                if (m_len>1) {
                    int sz = m_len/2;
                    int last = m_len-1;
                    for(int i=0; i<sz; ++i) {
                        char ch = m_data[i];
                        m_data[i] = m_data[last-i];
                        m_data[last-i] = ch;
                    }
                }
            } else append(0x30);
        }

        //==============================================
        //  ДЕСТРУКТОР
        //==============================================
        ~AsciiZ() {
                if (m_data) delete [] m_data;
                m_data = 0;
                m_len = 0;
                m_size = 0;
        }

        //==============================================
        //  ОПЕРАТОРЫ
        //==============================================
        /*! \brief Оператор присвоения (копирует данные. служебная информация
        после копирования может различаться (размер контейнера и пр.))
        */
        AsciiZ& operator = (const AsciiZ &other) {
            reset();
            append(other);
            return *this;
        }
        //! \brief Оператор сравнения
        bool operator == (const AsciiZ &other) {
            if (m_len != other.size()) return false;
            return (memcmp(m_data, other.data(), m_len) == 0);
        }
        //! \brief Оператор сравнения (только для ASCIIZ-строк)
        bool operator == (const char* pch) {
            int len = strlen(pch);
            if (m_len != len) return false;
            return (strcmp(m_data, pch) == 0);
        }
        /*! \brief Операция индекса для работы с неконстантными объектами
          (!!! Не выполняет проверку индекса)
         */
        char& operator [] (int i) {
            return *(m_data+i);
        }
        /*! \brief Операция индекса для работы с константными объектами
          (!!! Не выполняет проверку индекса)
         */
        char& operator [] (int i) const {
            return *(m_data+i);
        }

        //==============================================
        //  GET и SET
        //==============================================
        /*! \brief Получение символа по индексу.
          (!!! Проверка индекса выполняется)
         */
        char get(int i) {
            if (i<0 || i>=m_len) return 0;
            else return m_data[i];
        }
        /*! \brief Запись символа по индексу.
          (!!! Проверка индекса выполняется)
         */
        void set(int i, const char &ch) {
            if (i<0 || i>=m_len) return;
            else m_data[i] = ch;
        }

        //==============================================
        //  Варианты append()
        //==============================================
        // можно добавить данные, содержащие внутренние нули
        void append(const AsciiZ &str)
        {
                int sl = str.length();
                int delta = (m_len+sl+1)-m_size;
                if (delta>=0) {
                        if (m_size > delta) delta = m_size;
                        resize(delta);
                }
                memcpy(m_data+m_len, str.data(), sl);
                m_len += sl;
                m_data[m_len] = 0;
        }
        //! \brief Добавим часть строки const char *
        void append(int start, int count, const char *src) {
            // выполним смещение по заданному значению start
            while (*src && start>0) {
                --start;
                ++src;
            }
            if (count == 0) {
                while(*src) append(*src++);
            } else {
                while (*src) {
                    if (--count < 0) break;
                    append(*src++);
                }
            }
        }
        /*! \brief Добавление массива байт! Если len будет задана явно,
            то можно будет добавить данные, содержащие внутренние нули!
         */
        void append(const char *str, int len = -1)
        {
            if (str == 0) return;
            if (len<0) len = strlen(str);
            int delta = (m_len+len+1)-m_size;
            if (delta>=0) {
                if (m_size > delta) delta = m_size;
                resize(delta);
            }
            memcpy(m_data+m_len, str, len);
            m_len += len;
            m_data[m_len] = 0;
        }
        //! \brief Добавить символ ch
        void append(const char &ch)
        {
            // +1 на завершающий ноль!
            int delta = (m_len+1)-m_size;
            if (delta>=0) {
                if (m_size > delta) delta = m_size;
                resize(delta);
            }
            m_data[m_len++] = ch;
            m_data[m_len] = 0;
        }
        //! \brief Добавить символ заданный числом
        void append(int ch)
        {
            // +1 на завершающий ноль!
            int delta = (m_len+1)-m_size;
            if (delta>=0) {
                if (m_size > delta) delta = m_size;
                resize(delta);
            }
            m_data[m_len++] = (char)ch;
            m_data[m_len] = 0;
        }
        //! \brief Добавить символ ch count раз
        void append(const char &ch, int count)
        {
            if (count <= 0) return;
            // +1 на завершающий ноль!
            int delta = (m_len+count)-m_size;
            if (delta>=0) {
                if (m_size > delta) delta = m_size;
                resize(delta);
            }
            for(int i=0; i<count; ++i) {
                m_data[m_len++] = ch;
            }
            m_data[m_len] = 0;
        }
        /*! \brief Добавить символьное представление знакового целого числа в заданной системе счисления
          \param num - число, строковое представление которого следует добавить к данным класса
          \param width - желаемая ширина изображения числа. Слева будет дополнено нулями, при необходимости
          \param radix - основание системы счисления
          \return Возвращает количество знаков, которое было занято под изображение числа
         */
        int append(int num, int width, int radix)
        {
            if (radix <= 0) return -1;

            int sign = 0;
            if (num<0) {
                sign = 1;
                num = -num;
            }

            /* Посчитаем количество разрядов, которое займет запись числа num
             в системе счисления radix.
             !!! Добавление FLT_EPSILON обязательно. Иначе можно наткнуться на ошибку вычислений.
             Например, будет неправильно вычислена длина записи числа 1000 в 10-ой системе счисления.
            */
            int len;
            if (num == 0) len = 1;
            else len = (int)(log(num)/log(radix)+FLT_EPSILON)+1;

            // скорректируем len с учетом возможно большего width
            if (width>len) len = width;

            // проверим наличие памяти в классе под добавку длиной len
            int delta = (m_len+len+sign+1)-m_size;
            if (delta>=0) {
                if (m_size > delta) delta = m_size;
                resize(delta);
            }

            if (sign) m_data[m_len++] = '-';
            int m, index=1;
            while(index<=len) {
                m=num%radix;
                num=num/radix;
                m_data[m_len+len-index++] = (m<=9)?(m+0x30):(m+55);
            }

            m_len += len;
            m_data[m_len] = 0;

            return len+sign;
        }
        /*! \brief Добавить символьное представление знакового целого числа в заданной системе счисления
          \param num - число, строковое представление которого следует добавить к данным класса
          \param width - желаемая ширина изображения числа. Слева будет дополнено нулями, при необходимости
          \param radix - основание системы счисления
          \return Возвращает количество знаков, которое было занято под изображение числа
         */
        int append(unsigned long num, int width, int radix)
        {
            if (radix <= 0) return -1;

            /* Посчитаем количество разрядов, которое займет запись числа num
             в системе счисления radix.
             !!! Добавление FLT_EPSILON обязательно. Иначе можно наткнуться на ошибку вычислений.
             Например, будет неправильно вычислена длина записи числа 1000 в 10-ой системе счисления.
            */
            int len;
            if (num == 0) len = 1;
            else len = (int)(log(num)/log(radix)+FLT_EPSILON)+1;

            // скорректируем len с учетом возможно большего width
            if (width>len) len = width;

            // проверим наличие памяти в классе под добавку длиной len
            int delta = (m_len+len+1)-m_size;
            if (delta>=0) {
                if (m_size > delta) delta = m_size;
                resize(delta);
            }

            int m, index=1;
            while(index<=len) {
                m=num%radix;
                num=num/radix;
                m_data[m_len+len-index++] = (m<=9)?(m+0x30):(m+55);
            }

            m_len += len;
            m_data[m_len] = 0;

            return len;
        }

        //==============================================
        //  РАЗНОЕ
        //==============================================
        // усекаем строку до меньшего размера заданного аргументом len
        void truncate(int len)
        {
            if (len<0) return;

            if (m_len > len) {
                m_len = len;
                m_data[m_len] = 0;
            }
        }

        // Преобразование строки к целому.
        // if (count>0) - необходимо преобразовать count символов начиная с индекса start
        // if (count<=0) - необходимо преобразовать все символы строки начиная с индекса start.
        // После преобразованиея count содержит количество преобразованных символов
        // radix - основание системы счисления для преобразования (2..36)
        unsigned long toULong(int start, int &count, int radix=10) const
        {
            if (radix<2 || radix>36) {
                count = 0;
                return 0;
            }
            if (start>=m_len) {
                count = 0;
                return 0;
            }

            int up_to;
            if (count>0) {
                up_to = start+count;
                if (up_to>m_len) up_to = m_len;
            } else up_to = m_len;

            count = 0;
            unsigned long sum = 0;
            for(int i=start; i<up_to; ++i) {
                char ch = m_data[i];
                int res;
                if ( ch >= '0' && ch <= '9' ) res = ch-0x30;
                else if ( ch >= 'A' && ch <= 'Z' ) res = ch-55;
                else if ( ch >= 'a' && ch <= 'z' ) res = ch-87;
                else break;

                if ( res>=radix ) break;

                ++count;
                sum = sum*radix + res;
            }
            return sum;
        }

        //! \brief Сравнение начала строки с эталоном
        bool isLeft(const char *str) {
            if (str == 0) return false;
            int str_len = strlen(str);
            if (str_len>m_len) return false;

            bool res = true;
            for(int i=0; i<str_len; ++i) {
                if (m_data[i] != str[i]) {
                    res = false;
                    break;
                }
            }
            return res;
        }

        //! \brief Сравнение конца строки с эталоном
        bool isRight(const char *str) {
            if (str == 0) return false;
            int str_len = strlen(str);
            if (str_len>m_len) return false;

            int start_index = m_len - str_len;
            bool res = true;
            for(int i=0; i<str_len; ++i) {
                if (m_data[start_index + i] != str[i]) {
                    res = false;
                    break;
                }
            }
            return res;
        }

        //! \brief Получение текущей длины строки не учитывая завершающий ноль.
        int length() const {
            return m_len;
        }
        //! \brief Получение текущей длины строки не учитывая завершающий ноль (аналог length()).
        int size() const {
            return m_len;
        }
        //! \brief Получение размера распределенной области не учитывая завершающий ноль
        int containerSize() const {
            // Не учитывая завершающий ноль
            return m_size-1;
        }
        //! \brief Проверка пустой строки
        bool isEmpty() const {
            return m_len == 0;
        }

        void clear()
        {
            m_len = 0;
            m_data[m_len] = 0; // занулим строку
        }

        //! \brief Увеличивает размер контейнера до заданного размера с сохранением данных.
        /*!
          Если текущий размер контейнера больше заданного, то изменение размера не
          производится.

          Новый размер устанавливается на единицу больший заданного - резервируется
          место под завершающий ноль строки.

          \param iReservedSize - новый запрашиваемый размер контейнера.
          \return Если требуемый размер больше текущего возвращает \c true
         */
        bool reserve(int iReservedSize) {
            // +1 на завершающий ноль!
            if (iReservedSize+1 <= m_size) return false;

            resize(iReservedSize-m_size+1);
            return true;
        }
        //! \brief Очистить данные и принудительно установить новый размер
        /*!
          Новый размер устанавливается на единицу больший заданного - резервируется
          место под завершающий ноль строки.
          \param iNewSize - новый запрашиваемый размер контейнера.
          \return В случае корректно заданного размера возвращает \c true
         */
        bool clearAndNewSize(int iNewSize) {
            if (iNewSize<=0) return false;
            m_size = iNewSize + 1;
            delete [] m_data;
            m_data = new char[m_size];
            m_len = 0;
            return true;
        }

        void reset(int size = 256) {
            if (size<=0) {
                clear();
                return;
            }
            if (m_size <= size) {
                clear();
                return;
            }
            m_size = size;
            delete [] m_data;
            m_data = new char[m_size];
            m_len = 0;
        }

        const char* data() const {
                return m_data;
        }

        bool equals(const char *str) {
            int i__len_str = strlen(str);
            if (i__len_str != m_len) return false;
            for(int i=0; i<m_len; ++i) {
                if (str[i] != m_data[i]) return false;
            }
            return true;
        }

        bool equals(const AsciiZ &z) {
            int i__len = z.length();
            if (i__len != m_len) return false;
            const char *str = z.data();
            for(int i=0; i<m_len; ++i) {
                if (str[i] != m_data[i]) return false;
            }
            return true;
        }

private:
        char *m_data;
        // количество распределенных байт
        int m_size;
        // текущая длина строки
        int m_len;

        void resize(int delta) {
        	m_size += delta;
        	char *data = new char[m_size];
        	strcpy(data,m_data);
        	delete [] m_data;
        	m_data = data;
        }
        void init() {
            m_size = 256;
            m_data = new char[m_size];
            m_len = 0;
            m_data[m_len] = 0; // закроем строку
        }
};

#endif
